#pragma once 
#include <pthread.h>
#include <unordered_map>
#include <string>
#include "Util.hpp"
#include "config.hpp"

namespace cloud 
{
  using namespace shy;
  typedef struct BackupInfo_t
  {
    bool pack_flag;
    size_t fsize;
    time_t mtime;
    time_t atime;
    std::string real_path;
    std::string pack_path;
    std::string url;

    public: 
    void NewBackupInfo(const std::string& realpath)
    {
      Config* config = Config::GetInstance();
      std::string packdir = config->GetPackDir();
      std::string packsuffix = config->GetPackfileSuffix();
      std::string download_prefix = config->GetDownloadPrefix();
      FileUtil fu(realpath);
      this->pack_flag = false; 
      this->fsize = fu.FileSize();
      this->mtime = fu.LastModifyTime();
      this->atime = fu.LastAccessTime();
      this->real_path = realpath;
      this->pack_path = packdir + fu.FileName() + packsuffix; 
      this->url = download_prefix + fu.FileName();
    }
  }BackupInfo;

  class DataManager
  {
    private:
      std::string _backup_file;
      pthread_rwlock_t _rwlock;
      std::unordered_map<std::string,BackupInfo> _table;
    public:
      DataManager()
      {
        _backup_file = cloud::Config::GetInstance()->GetBackupFile();
        pthread_rwlock_init(&_rwlock , nullptr);
        InitLoad();
      }

      ~DataManager()
      {
        pthread_rwlock_destroy(&_rwlock);
      }
    public:
      bool Insert(const BackupInfo& info)
      {
        pthread_rwlock_wrlock(&_rwlock);
        _table[info.url] = info;
        pthread_rwlock_unlock(&_rwlock);
        Storage();
        return true;
      }
      bool Update(const BackupInfo& info)
      {
        pthread_rwlock_wrlock(&_rwlock);    
        _table[info.url] = info;    
        pthread_rwlock_unlock(&_rwlock);   
        Storage();
        return true;    
      }
      bool GetOneByURL(const std::string& url , BackupInfo* info)
      {
        pthread_rwlock_rdlock(&_rwlock);
        auto it = _table.find(url);
        if (it == _table.end())
        {
          return false;
        }
        *info = it->second;
        pthread_rwlock_unlock(&_rwlock);
        return true;
      }

      bool GetOneByRealPath(const std::string& realpath, BackupInfo* info)
      {
        pthread_rwlock_wrlock(&_rwlock);
        auto it = _table.begin();
        while (it != _table.end())
        {
          if (it->second.real_path == realpath)
          {
            *info = it->second;
            pthread_rwlock_unlock(&_rwlock);
            return true;
          }
          it++;
        }
        pthread_rwlock_unlock(&_rwlock);
        return false;
      }

      bool GetAll(std::vector<BackupInfo>* array)
      {
        pthread_rwlock_wrlock(&_rwlock);
        auto it = _table.begin();
        while (it != _table.end())
        {
          array->push_back(it->second);
          it++;
        }
        pthread_rwlock_unlock(&_rwlock);
        return true;
      }

      bool Storage()
      {
        // 1. get all date
        std::vector<BackupInfo> arry;
        this->GetAll(&arry);
        // 2. add to json::value
        Json::Value items;
        for (int i = 0; i < arry.size(); i++)
        {
          Json::Value root;
          root["pack_flag"] = arry[i].pack_flag;
          root["fszie"] = std::to_string( arry[i].fsize);
          root["atime"] = std::to_string(arry[i].atime);
          root["mtime"] = std::to_string(arry[i].mtime);
          root["real_path"] = arry[i].real_path;
          root["pack_path"] = arry[i].pack_path;
          root["url"] = arry[i].url;
          items.append(root); // append array elements
        }

        // 3. serialize the items 
        std::string body;
        jsonUtil::Serialize(items , body);
        // 4. write the file 
        FileUtil fu(_backup_file);
        fu.SetContent(body);
        return true; 
      }

      bool InitLoad()
      {
        // 1 get data from cloud.dat 
        FileUtil fu(_backup_file);
        if (fu.Exists() == false)
        {
          return true;
        }
        std::string body;
        fu.GetContent(body);
        // 2 unserialize 
        Json::Value root;
        jsonUtil::UnSerialize(body , root);
        // 3 add value to table
        int count = root.size();
        for (int i = 0; i < count; i++)
        {
          cloud::BackupInfo info;
          info.pack_flag = root[i]["pack_flag"].asBool();
          info.fsize = std::stoi(root[i]["fszie"].asString());
          info.mtime = std::stoll(root[i]["mtime"].asString());
          info.atime = std::stoll(root[i]["atime"].asString());
          info.pack_path = root[i]["pack_path"].asString();
          info.real_path = root[i]["real_path"].asString();
          info.url = root[i]["url"].asString();
          Insert(info);
        }
      }

  };
}


